﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml;


namespace ClassLibrary
{
    /* Class for describing individuals */
    public class Individual
    {
        public int id { set; get; }
        public string name { set; get; }
        public string path { set; get; }


        public Population population;
        

        // each individual has 2*N chromosomes
        public int amount_chromosome_pairs { set; get; }
        public Dictionary<string, Chromosome[]> massive_chromosomes;


        // each individual has its own set of phenotype characteristics
        public int amount_characteristics { set; get; }
        public Dictionary<string, Characteristic> str_massive_characteristics = new Dictionary<string, Characteristic>();
        public Dictionary<int, Characteristic> int_massive_characteristics = new Dictionary<int, Characteristic>();

        // in every life cycle a special event happens with indiviual
        public Event individual_event { set; get; }

        // generator of random numbers
        static Random random_generator = new Random(DateTime.Now.Ticks.GetHashCode());


        /* Constructor #1 */
        public Individual(string path_to_individual, Population pop)
        {
            path = path_to_individual;
            population = pop;
        }
        

        /* Constructor #2 */
        public Individual(int individual_id, string individual_name, Population pop, Dictionary<string,Chromosome[]> chromosome_massive)
        {
            id = individual_id;
            population = pop;
            name = individual_name;

            amount_chromosome_pairs = chromosome_massive.Count();
            massive_chromosomes = chromosome_massive;

            foreach (Chromosome[] chromosome_pair in chromosome_massive.Values)
            {
                foreach (Chromosome chromosome in chromosome_pair)
                {
                    chromosome.individual = this;
                }
            }

            createCharacteristicMassive();
        }


        /* 
         * Method 
         * Args: no
         * Does: initialization from XML file
        */
        public void initIndividual()
        {
            // data for phenotype characteristics
            createCharacteristicMassive();

            // open XML file            
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(File.ReadAllText(path));

            // reading attributes of the first node
            name = doc.DocumentElement.GetAttribute("Name");
            amount_chromosome_pairs = Convert.ToInt32(doc.DocumentElement.GetAttribute("ChromosomePairsAmount"));
            massive_chromosomes = new Dictionary<string, Chromosome[]>();

            // reading all data about chromosome_pares
            if (amount_chromosome_pairs != 0)
            {
                ChromosomeTable ch_table = population.species.sp_chromosome_table;                

                // reading "indiviual_gene_set" node                
                
                foreach (XmlNode xml_node in doc.GetElementsByTagName("chromosome_pair"))
                {
                                    
                    massive_chromosomes.Add(xml_node.Attributes[0].Value, new Chromosome[2]);                    

                    int j = -1;
                    foreach (XmlNode child_xml_node in xml_node.ChildNodes)
                    {
                        j++;
                        List<int> allels = new List<int>();
                        foreach (string str in child_xml_node.ChildNodes[0].InnerText.Split(' '))
                        {
                            allels.Add(Convert.ToInt32(str));
                        }
                        massive_chromosomes[xml_node.Attributes[0].Value][j] = new Chromosome(this, Convert.ToInt32(child_xml_node.Attributes[0].Value), allels.ToArray());
                    }
                }
            }      
        }


        /*
         * Method
         * Args: no
         * Does: creates characteristic massive
        */
        public void createCharacteristicMassive()
        {
            amount_characteristics = population.species.sp_phenotype_table.characteristics_amount;
            Dictionary<string, CharacteristicDescription> characteristic_table = population.species.sp_phenotype_table.characteristic_table;

            foreach (string characteristic_name in characteristic_table.Keys)
            {
                int characteristic_id = characteristic_table[characteristic_name].id;
                Characteristic characteristic = new Characteristic(characteristic_id, characteristic_name);

                str_massive_characteristics.Add(characteristic_name, characteristic);
                int_massive_characteristics.Add(characteristic_id, characteristic);
            }
        }


        /* 
         * Method 
         * Args: no
         * Does: counts set of phenotype characteristics for individual
        */
        public void countPhenotypeCharacteristics()
        {
            // preparing massive for counting alleles
            Dictionary<int, UInt64[]> gene_alleles = createMassForCountingGeneAllels();
            
            // counting allels
            countGeneAllels(gene_alleles);

            // creating control string list which describes individual's genome
            List<string> control_str = createContolStringList(gene_alleles);

            // looking for concidences in genome's set and genome_phenotype_link
            // and getting characteristic's values
            countPhenotypeCharacteristicsByControlStringList(control_str);            
        }


        /* 
         * Auxilary Method #1 to countPhenotypeCharacteristics()
         * Args: no
         * Does: preparing massive for counting alleles
         * Returns: massive for counting all allels
        */
        public Dictionary<int,UInt64[]> createMassForCountingGeneAllels()
        {
            GeneTable sp_gene_table = population.species.sp_gene_table;
            Dictionary<int, UInt64[]> gene_alleles = new Dictionary<int, UInt64[]>();
            foreach (GeneType g in sp_gene_table.gene_table)
            {
                gene_alleles.Add(g.allele_type_id,new UInt64[g.amount_alelles]);
            }
            

            return gene_alleles;
        }


       /* 
        * Auxilary Method #2 to countPhenotypeCharacteristics()
        * Args: massive for counting all allels
        * Does: counting allels
       */
        public void countGeneAllels(Dictionary<int, UInt64[]> gene_alleles)
        {
            int allele_type_id;
            int allele;
            foreach (Chromosome[] pair in massive_chromosomes.Values)
            {
                foreach (Chromosome chrom in pair)
                {
                    foreach (Locus l in chrom.massive_locus)
                    {
                        allele_type_id = l.gene.allele_type_id;
                        allele = l.gene.allele;
                        gene_alleles[allele_type_id][allele]++;
                    }
                }
            }
        }


      /* 
       * Auxilary Method #3 to countPhenotypeCharacteristics()
       * Args: massive for counting all allels
       * Does: creates control string list which describes individual's genome
      */
        public List<string> createContolStringList(Dictionary<int, UInt64[]> gene_alleles)
        {
            List<string> control_str = new List<string>();

            GeneTable sp_gene_table = population.species.sp_gene_table;
            string str;
            foreach (GeneType g in sp_gene_table.gene_table)
            {
                for (int j = 0; j < g.amount_alelles; j++)
                {
                    // number of the allele
                    str = j.ToString();

                    // gene's name
                    str += g.name;

                    // amount of the alleles
                    str += gene_alleles[g.allele_type_id][j].ToString();

                    // adding piece of gene set to control string list
                    control_str.Add(str);
                }
            }
            return control_str;
        }


      /* 
       * Auxilary Method #4 to countPhenotypeCharacteristics()
       * Args: control string list
       * Does: looking for concidences in genome's set and genome_phenotype_link
       *       and getting characteristic's values 
      */
        public void countPhenotypeCharacteristicsByControlStringList( List<string> control_str)
        {
            Dictionary<string, CharacteristicLink> link = population.species.sp_genome_phenotype_link.link;
            foreach (string characteristic_name in link.Keys)
            {
                // getting mapping only for single characteristic
                Dictionary<int, List<string>> ge_ch_map = link[characteristic_name].genome_charactrectistic_map;

                foreach (int combination_degree in ge_ch_map.Keys)
                {
                    bool full_concidence = true;
                    foreach (string combination_piece in ge_ch_map[combination_degree])
                    {
                        if (!control_str.Contains(combination_piece))
                        {
                            full_concidence = false;
                            break;
                        }
                    }

                    if (full_concidence)
                    {
                        str_massive_characteristics[characteristic_name].degree = combination_degree;
                        break;
                    }
                }
            }
        }


       /*
        *  Method
        *  Args: no
        *  Does: defines event vector for individual
        *  Returns: event
       */
        public void getEvent()
        {
            NaturalSelectionLink natural_selection_link = population.pop_natural_selection_link;

            int[] characteristic_vector = getCharacteristicVector();

            individual_event = natural_selection_link.mapCharacteristicVectorIntoEventVector(characteristic_vector);
        }


        /*
        *  Method
        *  Args: no
        *  Does: count characteristic vector
        *  Returns: int[]
       */
        public int[] getCharacteristicVector()
        {
            int[] characteristic_vector = new int[amount_characteristics];

            for (int i = 0; i < amount_characteristics; i++)
            {
                characteristic_vector[i] = int_massive_characteristics[i].degree;
            }

            return characteristic_vector;
        }


        /*
        *  Method
        *  Args: no
        *  Does: generates haploid set of genes in chromosomes for child 
        *  Returns: chromosome massive
       */
        public Chromosome[] generateChildChromosomeSet()
        {
            Chromosome[] child_chromosome_massive = new Chromosome[amount_chromosome_pairs];

            for (int i = 0; i < amount_chromosome_pairs; i++)
            {
                int chromosome_pare_type_id = i;                

                child_chromosome_massive[chromosome_pare_type_id] = generateOneChildChromosome(chromosome_pare_type_id);
            }

            return child_chromosome_massive;
        }


        /*
        *  Method
        *  Args: id of chromosome's pare type
        *  Does: generates haploid set of genes for one chromosome of child 
        *  Returns: one child chromosome
       */
        public Chromosome generateOneChildChromosome( int chromosome_pare_type_id)
        {
            ChromosomeTable chromosome_table = population.species.sp_chromosome_table;
            CrossingoverLink crossingover_link = population.species.sp_crossingover_link;

            // geetting amount of locuses in chromosome and corresponding parent chromosome pair
            int amount_locuses = chromosome_table.getChromosomePairType(chromosome_pare_type_id).amount_locuses;
            Chromosome[] parent_chromosome_pair = massive_chromosomes[chromosome_pare_type_id.ToString()];

            int[][] alleles = new int[2][];
            int[] child_alleles;
            alleles[0] = new int[amount_locuses];
            alleles[1] = new int[amount_locuses];


            // getting the pare of allele's set
            for (int j = 0; j < amount_locuses; j++)
            {
                int locus_id = j;

                double probability = 100 * random_generator.NextDouble();
                double crossingover_probability = crossingover_link.getCrossingoverProbability(chromosome_pare_type_id, locus_id);

                if (probability < crossingover_probability)
                {
                    alleles[0][j] = parent_chromosome_pair[0].getAlleleIdByLocusId(j);
                    alleles[1][j] = parent_chromosome_pair[1].getAlleleIdByLocusId(j);
                }
                else
                {
                    alleles[0][j] = parent_chromosome_pair[1].getAlleleIdByLocusId(j);
                    alleles[1][j] = parent_chromosome_pair[0].getAlleleIdByLocusId(j);
                }
            }

            // choosing one of the allele's sets for the child
            {
                double probability = 100 * random_generator.NextDouble();

                if (probability < 50)
                {
                    child_alleles = alleles[0];
                }
                else
                {
                    child_alleles = alleles[1];
                }
            }

            Chromosome oneChildChromosome = new Chromosome(this, chromosome_pare_type_id, child_alleles);

            oneChildChromosome.individual = null;

            return oneChildChromosome;
		}


        /*
         * Method 
         * Args: no
         * Does: counts massive with values of characteristics
         * Returns: massive consisting of characteristic values
        */
        public int[] getMassiveCharacteristicValues()
        {
            int[] characteristic_values = new int[amount_characteristics];

            foreach (int ch_id in int_massive_characteristics.Keys)
            {
                characteristic_values[ch_id] = int_massive_characteristics[ch_id].degree;
            }

            return characteristic_values;
        }
    }
}